using System;
using System.Collections.Generic;
using System.Text;
using SP.Parameter;

namespace SP.HwLowProfileRS
{
    public class Reactor : SP.AbstractReactor
    {
        public Reactor()
            : base()
        {
        }

        public override string Name
        {
            get { return "HwLowProfileRS"; }
        }

        public override string[] CodeFiles
        {
            get { return new string[] { ".material", ".shd" }; }
        }

        public override void InitShader(Shader shader)
        {
            if (null == shader)
                return;

            shader.Fusions.Clear();

            shader.Reactor = this.Name;

            shader.Fusions.Add(new BaseMapFusion().SetEnabled(false));
            shader.Fusions.Add(new ReflectionMapVertexFusion().SetEnabled(false));
            shader.Fusions.Add(new ReflectionMapPixelFusion().SetEnabled(false));
            shader.Fusions.Add(new LightMapFusion().SetEnabled(false));
            shader.Fusions.Add(new AlphaMapFusion().SetEnabled(false));
            shader.Fusions.Add(new VertexAlphaFusion().SetEnabled(false));
            shader.Fusions.Add(new RimLightingVertexFusion().SetEnabled(false));
            shader.Fusions.Add(new RimLightingPixelFusion().SetEnabled(false));
            shader.Fusions.Add(new UVScrollingFusion().SetEnabled(false));
        }

        public override void Generate(Shader shader, string codeFile, StringBuilder b)
        {
            mShader = shader;

            switch(codeFile)
            {
                case ".material":
                    CodeMaterial(b);
                    break;

                case ".shd":
                    CodeShd(b);
                    break;
            }
        }

        private const string SCHEME_DEFAULT = "Default";

        #region Material
        private void CodeMaterial(StringBuilder b)
        {
            b.AppendLine("//--------//--------//--------//--------//--------//--------//--------//--------");
            b.AppendLine("vertex_program " + mShader.Name + "/Vert hlsl");
            b.AppendLine("{");
            b.AppendLine("source " + mShader.Name + ".shd");
            b.AppendLine("entry_point Vert");
            b.AppendLine("target vs_1_1");
            b.AppendLine("preprocessor_defines HLSL=1");
            b.AppendLine("} // vertex_program");

            b.AppendLine("//--------//--------//--------//--------//--------//--------//--------//--------");
            b.AppendLine("fragment_program " + mShader.Name + "/Frag_" + SCHEME_DEFAULT + " hlsl");
            b.AppendLine("{");
            b.AppendLine("source " + mShader.Name + ".shd");
            b.AppendLine("entry_point Frag_" + SCHEME_DEFAULT);
            b.AppendLine("target ps_1_1");
            b.AppendLine("preprocessor_defines HLSL=1");
            b.AppendLine("} // fragment_program");

            b.AppendLine("//--------//--------//--------//--------//--------//--------//--------//--------");
            b.AppendLine("material " + mShader.Name);
            b.AppendLine("{");

            // default technique
            b.AppendLine("technique Default");
            b.AppendLine("{");
            b.AppendLine("scheme Default");
            b.AppendLine("pass Pass0");
            b.AppendLine("{");

            if (mShader.DepthTest)
                b.AppendLine("depth_check on");
            else
                b.AppendLine("depth_check off");

            if (mShader.Transparent)
            {
                b.AppendLine("depth_write off");
                b.AppendLine("scene_blend alpha_blend");
            }
            else
            {
                b.AppendLine("depth_write on");
            }

            if (mShader.TwoSided)
            {
                b.AppendLine("cull_hardware none");
                b.AppendLine("cull_software none");
            }

            b.AppendLine("//--------");
            b.AppendLine("vertex_program_ref " + mShader.Name + "/Vert");
            b.AppendLine("{");
            CodeMaterialConstants(b);
            CodeMaterialParameters(b);
            b.AppendLine("} // vertex_program_ref");

            b.AppendLine("//--------");
            b.AppendLine("fragment_program_ref " + mShader.Name + "/Frag_Default");
            b.AppendLine("{");
            CodeMaterialConstants(b);
            CodeMaterialParameters(b);
            b.AppendLine("} // fragment_program_ref");

            CodeMaterialTextureUnits(b);

            b.AppendLine("} // pass");
            b.AppendLine("} // technique");

            b.AppendLine("} // material");
        }

        private void CodeMaterialParameters(StringBuilder b)
        {
            foreach (IFusion f in mShader.Fusions)
            {
                if (null != f && f.Enabled)
                {
                    foreach (IParameter p in f.Parameters)
                    {

                        if (p.GetType() == typeof(ScalarParameter))
                        {
                            b.AppendLine(String.Format("param_named {0} float {1}", p.Name, p.Value));
                        }
                        else if (p.GetType() == typeof(Vec2Parameter))
                        {
                            b.AppendLine(String.Format("param_named {0} float2 {1}", p.Name, p.Value.Replace(",", "")));
                        }
                        else if (p.GetType() == typeof(Vec3Parameter))
                        {
                            b.AppendLine(String.Format("param_named {0} float3 {1}", p.Name, p.Value.Replace(",", "")));
                        }
                        else if (p.GetType() == typeof(Vec4Parameter))
                        {
                            b.AppendLine(String.Format("param_named {0} float4 {1}", p.Name, p.Value.Replace(",", "")));
                        }
                        else if (p.GetType() == typeof(ColorParameter))
                        {
                            b.AppendLine(String.Format("param_named {0} float4 {1}", p.Name, p.Value.Replace(",", "")));
                        }
                    }
                }
            }
        }

        private void CodeMaterialTextureUnits(StringBuilder b)
        {
            // texture unit
            int texunit = 0;

            // before output the texture units decl... sort it by the texcoord
            const string empty_tex_unit_decl = "texture_unit{}\n";

            // Usually, there are 4 texture units only..
            Dictionary<int, StringBuilder> textureUnitDecls = new Dictionary<int, StringBuilder>(4);
            int max_tunit = -1;

            foreach (IFusion f in mShader.Fusions)
            {
                if (null != f && f.Enabled)
                {
                    foreach (IParameter p in f.Parameters)
                    {
                        StringBuilder bb = new StringBuilder();
                        
                        if (p.GetType() == typeof(Texture2dParamter))
                        {
                            Texture2dParamter ptex2d = p as Texture2dParamter;

                            //todo: handle collided sampler for ptex2d.ForceSamplerId
                            int tunit = texunit++;
                            if (ptex2d.ForceSamplerId >= 0) tunit = ptex2d.ForceSamplerId;

                            bb.AppendLine("//--------");
                            bb.Append(String.Format
                                ("texture_unit {0}\n{{\ntexture {1} 2d {2}\nfiltering {3} {4} {5}\ntex_address_mode {6} {7}\ntex_coord_set {8}\n}} // texture_unit\n"
                                , ptex2d.Name
                                , System.IO.Path.GetFileName(ptex2d.Path)
                                , ptex2d.SRGBTexture ? "gamma" : ""
                                , GetTextureFilterName(ptex2d.MinFilter).ToLower()
                                , GetTextureFilterName(ptex2d.MagFilter).ToLower()
                                , GetTextureFilterName(ptex2d.MipFilter).ToLower()
                                , GetTextureAddressMode(ptex2d.UAddressMode).ToLower()
                                , GetTextureAddressMode(ptex2d.VAddressMode).ToLower()
                                , tunit
                                ));

                            max_tunit = Math.Max(max_tunit, tunit);
                            textureUnitDecls.Add(tunit, bb);
                        }
                        else if (p.GetType() == typeof(TextureCubeParamter))
                        {
                            TextureCubeParamter ptexCube = p as TextureCubeParamter;

                            //todo: handle collided sampler for ptex2d.ForceSamplerId
                            int tunit = texunit++;
                            if (ptexCube.ForceSamplerId >= 0) tunit = ptexCube.ForceSamplerId;

                            bb.AppendLine("//--------");
                            bb.Append(String.Format
                                ("texture_unit {0}\n{{\ntexture {1} cubic {2}\nfiltering {3} {4} {5}\ntex_address_mode {6} {7} {8}\ntex_coord_set {9}\n}} // texture_unit\n"
                                , ptexCube.Name
                                , System.IO.Path.GetFileName(ptexCube.Path)
                                , ptexCube.SRGBTexture ? "gamma" : ""
                                , GetTextureFilterName(ptexCube.MinFilter).ToLower()
                                , GetTextureFilterName(ptexCube.MagFilter).ToLower()
                                , GetTextureFilterName(ptexCube.MipFilter).ToLower()
                                , GetTextureAddressMode(ptexCube.UAddressMode).ToLower()
                                , GetTextureAddressMode(ptexCube.VAddressMode).ToLower()
                                , GetTextureAddressMode(ptexCube.WAddressMode).ToLower()
                                , tunit
                                ));

                            max_tunit = Math.Max(max_tunit, tunit);
                            textureUnitDecls.Add(tunit, bb);
                        }   
                    }
                }
            }

            for (int register_id = 0; register_id <= max_tunit; register_id++)
            {
                if (textureUnitDecls.ContainsKey(register_id))
                {
                    b.AppendLine(textureUnitDecls[register_id].ToString());
                }
                else
                {
                    b.AppendLine(empty_tex_unit_decl);
                }
            }
        }

        private static void CodeMaterialConstants(StringBuilder b)
        {
            b.AppendLine("param_named_auto cWorldMtx world_matrix ");
            b.AppendLine("param_named_auto cWorldViewMtx worldview_matrix ");
            b.AppendLine("param_named_auto cWorldViewProjMtx worldviewproj_matrix");
            b.AppendLine("param_named_auto cViewMtx view_matrix ");
            b.AppendLine("param_named_auto cViewIMtx inverse_view_matrix");
            b.AppendLine("param_named_auto cProjMtx projection_matrix");
            b.AppendLine("param_named_auto cTime time");
            //b.AppendLine("param_named_auto cViewportSize viewport_size");
            b.AppendLine("param_named_auto cCustom0 custom 0");
            b.AppendLine("param_named_auto cCustom1 custom 1");
            b.AppendLine("param_named_auto cCustom2 custom 2");
            b.AppendLine("param_named_auto cCustom3 custom 3");
        }

        #endregion // Material

        #region Shd

        private void CodeShd(StringBuilder b)
        {
            CodeShdPreprocessor(b);

            CodeShdHeader(b);

            CodeShdParameter(b);

            CodeShdConstant(b);

            CodeShdVertHeader(b);
            CodeShdVertBody(b);
            CodeShdVertFooter(b);

            CodeShdFragHeader(b, SCHEME_DEFAULT);
            CodeShdFragVarying(b);
            CodeShdFragBody(b, SCHEME_DEFAULT);
            CodeShdFragFooter(b, SCHEME_DEFAULT);

            CodeShdFooter(b);
        }

        private void CodeShdHeader(StringBuilder b)
        {
            b.AppendLine("//--------//--------//--------//--------//--------");
            b.AppendLine("//Fx Header");

            b.AppendLine("typedef float4x4 mat4;");
            b.AppendLine("typedef float3x3 mat3;");
            b.AppendLine("typedef float4 vec4;");
            b.AppendLine("typedef float3 vec3;");
            b.AppendLine("typedef float2 vec2;");

            b.AppendLine("vec4 xform(mat4 m, vec4 v) { return mul(m, v); }");
            b.AppendLine("vec3 xform(mat3 m, vec3 v) { return mul(m, v); }");

            b.AppendLine("struct VpIn");
            b.AppendLine("{");
            b.AppendLine("    vec4 Pos          : POSITION;");
            b.AppendLine("    vec4 Color        : COLOR0;");
            b.AppendLine("    vec3 Norm         : NORMAL;");
            b.AppendLine("    vec2 UVDiffuse    : TEXCOORD0;"); // Diffuse map
            b.AppendLine("    vec2 UVLight      : TEXCOORD1;"); // Light map
            b.AppendLine("};");

            b.AppendLine("struct VpOut");
            b.AppendLine("{");
            b.AppendLine("    vec4 Pos          : POSITION;");
            b.AppendLine("    vec4 Color        : COLOR0;");
            b.AppendLine("    vec4 Rim          : COLOR1;");
            b.AppendLine("    vec2 UVDiffuse    : TEXCOORD0;"); // Diffuse
            b.AppendLine("    vec2 UVAlpha      : TEXCOORD1;"); // Alpha
            b.AppendLine("    vec2 UVLight      : TEXCOORD2;"); // Light map
            b.AppendLine("    vec3 ReflectionDir: TEXCOORD3;");
            b.AppendLine("};");

            b.AppendLine("struct VpPipeline");
            b.AppendLine("{");
            b.AppendLine("    vec4 Pos_Ms;");
            b.AppendLine("    vec3 N_Ms;");
            b.AppendLine("    vec4 Color;");
            b.AppendLine("    vec3 ReflectionDir;");
            b.AppendLine("    vec4 Rim;");
            b.AppendLine("    vec3 N_Ws;");
            b.AppendLine("    vec3 VDir_Ws;");
            b.AppendLine("};");

            b.AppendLine("struct FpIn");
            b.AppendLine("{");
            b.AppendLine("    vec4 Color        : COLOR0;");
            b.AppendLine("    vec4 Rim          : COLOR1;");
            b.AppendLine("    vec2 UVDiffuse    : TEXCOORD0;"); // Diffuse
            b.AppendLine("    vec2 UVAlpha      : TEXCOORD1;"); // Alpha
            b.AppendLine("    vec2 UVLight      : TEXCOORD2;"); // Light map
            b.AppendLine("    vec3 ReflectionDir: TEXCOORD3;");
            b.AppendLine("};");

            b.AppendLine("struct FpOut");
            b.AppendLine("{");
            b.AppendLine("    vec4 C0           : COLOR0;");
            b.AppendLine("};");

            b.AppendLine("struct FpPipeline");
            b.AppendLine("{");
            b.AppendLine("  vec2  Texcoord[3];");
            b.AppendLine("  vec3  Albedo;");
            b.AppendLine("  float Opacity;");
            b.AppendLine("  vec3  Reflection;");
            b.AppendLine("  vec3  FragColor;");
            b.AppendLine("  vec3  Illumination;");
            b.AppendLine("};");
        }

        private void CodeShdPreprocessor(StringBuilder b)
        {
            b.AppendLine("//--------//--------//--------//--------//--------");
            b.AppendLine("// Preprocessors");

            foreach (IFusion f in mShader.Fusions)
            {
                if (null != f && f.Enabled)
                {
                    foreach (Preprocessor p in f.Preprocessors)
                    {
                        //string line = "#define " + p.Name + " " + p.Value.ToString();
                        string line = String.Format("#define {0} {1}", p.Name, p.Value);
                        b.AppendLine(line);
                    }
                }
            }
        }

        private void CodeShdParameter(StringBuilder b)
        {
            b.AppendLine("//--------//--------//--------//--------//--------");
            b.AppendLine("// Parameters");

            int texunit = 0;

            foreach (IFusion f in mShader.Fusions)
            {
                if (null != f && f.Enabled)
                {
                    foreach (IParameter p in f.Parameters)
                    {
                        if (p.GetType() == typeof(Texture2dParamter))
                        {
                            Texture2dParamter ptex2d = p as Texture2dParamter;

                            //todo: handle collided sampler for ptex2d.ForceSamplerId
                            int tunit = texunit++;
                            if (ptex2d.ForceSamplerId >= 0) tunit = ptex2d.ForceSamplerId;

                            b.AppendLine(String.Format
                                ("uniform sampler2D {0} : register( s{1} );"
                                , ptex2d.SamplerName
                                , tunit
                                ));
                        }
                        else if (p.GetType() == typeof(TextureCubeParamter))
                        {
                            TextureCubeParamter ptexCube = p as TextureCubeParamter;

                            //todo: handle collided sampler for ptex2d.ForceSamplerId
                            int tunit = texunit++;
                            if (ptexCube.ForceSamplerId >= 0) tunit = ptexCube.ForceSamplerId;

                            b.AppendLine(String.Format
                                ("uniform samplerCUBE {0} : register( s{1} );"
                                , ptexCube.SamplerName
                                , tunit
                                ));
                        }
                        else if (p.GetType() == typeof(ScalarParameter))
                        {
                            b.AppendLine(String.Format("uniform float {0} = {1};", p.Name, p.Value));
                        }
                        else if (p.GetType() == typeof(Vec2Parameter))
                        {
                            b.AppendLine(String.Format("uniform vec2 {0} = vec2({1});", p.Name, p.Value));
                        }
                        else if (p.GetType() == typeof(Vec3Parameter))
                        {
                            b.AppendLine(String.Format("uniform vec3 {0} = vec3({1});", p.Name, p.Value));
                        }
                        else if (p.GetType() == typeof(Vec4Parameter))
                        {
                            b.AppendLine(String.Format("uniform vec4 {0} = vec4({1});", p.Name, p.Value));
                        }
                        else if (p.GetType() == typeof(ColorParameter))
                        {
                            b.AppendLine(String.Format("uniform vec4 {0} = vec4({1});", p.Name, p.Value));
                        }
                    }
                }
            }
        }

        private void CodeShdFooter(StringBuilder b)
        {
        }

        private void CodeShdConstant(StringBuilder b)
        {
            b.AppendLine("//--------//--------//--------//--------//--------");
            b.AppendLine("// Constants");
            b.AppendLine("uniform mat4 cWorldMtx;");
            b.AppendLine("uniform mat4 cWorldViewMtx;");
            b.AppendLine("uniform mat4 cWorldViewProjMtx;");
            b.AppendLine("uniform mat4 cViewMtx;");
            b.AppendLine("uniform mat4 cViewIMtx;");
            b.AppendLine("uniform mat4 cProjMtx;");
            b.AppendLine("uniform float cTime;");
            b.AppendLine("uniform float4 cCustom0; // Reserved per-object color & alpha");
            b.AppendLine("uniform float4 cCustom1; // Reserved per-object user constant");
            b.AppendLine("uniform float4 cCustom2; // Reserved per-object user constant");
            b.AppendLine("uniform float4 cCustom3; // Reserved per-object user constant");
        }

        #region Vertex Program
        private void CodeShdVertHeader(StringBuilder b)
        {
            b.AppendLine("//--------//--------//--------//--------//--------");
            b.AppendLine("// Vertex0 program");
            b.AppendLine("void Vert(VpIn input, out VpOut output)");
            b.AppendLine("{");
            b.AppendLine("VpPipeline pipeline;");
        }

        private void CodeShdVertFooter(StringBuilder b)
        {
            b.AppendLine("}");
        }

        private void CodeShdVertBody(StringBuilder b)
        {
            b.AppendLine("// Vertex0 pipeline init");
            b.AppendLine("pipeline.Pos_Ms = input.Pos.xyzw;");
            b.AppendLine("pipeline.N_Ms = input.Norm.xyz;");
            b.AppendLine("pipeline.Color = input.Color;");
            b.AppendLine("pipeline.Rim = vec4(1, 1, 1, 0);");
            b.AppendLine("pipeline.ReflectionDir = vec3(0, 0, 0);");

            // calculate the world normal and world cam view dir for rim lighting and env mapping
            b.AppendLine("pipeline.N_Ws = xform(cWorldMtx, vec4(pipeline.N_Ms, 0));");
            b.AppendLine("pipeline.VDir_Ws = normalize(transpose(cViewIMtx)[3].xyz - xform(cWorldMtx, pipeline.Pos_Ms).xyz);");

            // vertex fusions
            foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.Vertex0))
                AppendFeatureCode(f, b);

            foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.Vertex1))
                AppendFeatureCode(f, b);

            foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.Vertex2))
                AppendFeatureCode(f, b);

            foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.Vertex3))
                AppendFeatureCode(f, b);


            b.AppendLine("// Vertex0 pipeline output");
            b.AppendLine("output.Pos  = xform(cWorldViewProjMtx, pipeline.Pos_Ms);");
            b.AppendLine("output.Color = pipeline.Color;");

            b.AppendLine("output.UVDiffuse = input.UVDiffuse;");
            b.AppendLine("output.UVAlpha = input.UVDiffuse;");
            b.AppendLine("output.UVLight = input.UVLight;");
            b.AppendLine("output.ReflectionDir = pipeline.ReflectionDir;");
            b.AppendLine("output.Rim = pipeline.Rim;");
        }

        #endregion // Vertex0 Program

        #region Fragment Program
        private void CodeShdFragHeader(StringBuilder b, string scheme)
        {
            b.AppendLine("//--------//--------//--------//--------//--------");
            b.AppendLine("// Fragment program " + scheme);
            b.AppendLine("void Frag_" + scheme + "(FpIn input, out FpOut output)");
            b.AppendLine("{");
        }

        private void CodeShdFragVarying(StringBuilder b)
        {
            b.AppendLine("// Fragment pipeline init");
            b.AppendLine("FpPipeline pipeline;");
            b.AppendLine("pipeline.Texcoord[0] = input.UVDiffuse;");
            b.AppendLine("pipeline.Texcoord[1] = input.UVAlpha;");
            b.AppendLine("pipeline.Texcoord[2] = input.UVLight;");

            b.AppendLine("pipeline.Albedo = cCustom0.rgb;");
            b.AppendLine("pipeline.Opacity = cCustom0.a;");
            b.AppendLine("pipeline.Reflection = vec3(0,0,0);");
            b.AppendLine("pipeline.FragColor = vec3(0,0,0);");
        }

        private void CodeShdFragBody(StringBuilder b, string scheme)
        {
            if (SCHEME_DEFAULT == scheme)
            {
                // parallax
                foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.Parallex))
                    AppendFeatureCode(f, b);

                // opacity
                foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.Opacity))
                    AppendFeatureCode(f, b);

                // albedo & gloss
                foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.AlbedoGloss))
                    AppendFeatureCode(f, b);

                // illumination
                foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.Illumination))
                    AppendFeatureCode(f, b);

                // set illumination to 1 if no illumination-features exists
                if (0 == Utils.GetEnabledFusionsByClass(mShader, Stage.Illumination).Count)
                    b.AppendLine("pipeline.Illumination = vec3(1,1,1);");

                // reflection
                foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.Relfection))
                    AppendFeatureCode(f, b);

                // frag color
                b.AppendLine("pipeline.FragColor = saturate(pipeline.Albedo * pipeline.Illumination + pipeline.Reflection);");

                // override
                foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.Override))
                    AppendFeatureCode(f, b);
            }
        }

        private void CodeShdFragFooter(StringBuilder b, string scheme)
        {
            b.AppendLine("// Fragment pipeline output");

            if (SCHEME_DEFAULT == scheme)
            {
                b.AppendLine("output.C0.rgb = pipeline.FragColor;");
                b.AppendLine("output.C0.a = pipeline.Opacity;");
            }

            b.AppendLine("}");
        }

        #endregion // Fragment Program

        #endregion // Shd

    }
}
